// TODO: CODE REVIEW & CLEANUP!
//
// VirtualSerialPort.cs
//
// Implements the VirtualSerialPort abstract base class and one implementation of it,
// ActualSerialPort.
//

using System;
using System.IO;
using System.IO.Ports;

namespace HomeUX.Utilities
{

/// <summary>
/// A base class for an object that can provide functionality similar to that of <n>SerialPort</n>.
/// A class that's based on this class can simulate a device connected to a serial port.
#if !SILVERLIGHT
/// One implemention of this base class is <r>ActualSerialPort</r>, which connects to a real serial
/// port.
#endif
/// </summary>
///
/// <remarks>
/// <para>
/// Classes based on <r>VirtualSerialPort</r> will support <i>character-based text mode</i>,
/// <i>line-based text mode</i>, <i>binary mode</i>, or some combination of the three.  See
/// <r>SupportsCharacterBasedTextMode</r>, <r>SupportsLineBasedTextMode</r>, and
/// <r>SupportsBinaryMode</r>.
/// </para>
/// <para>
/// When the application is finished using a <r>VirtualSerialPort</r> instance, <n>Dispose</n>
/// must be called on it.
/// </para>
/// </remarks>
///
public abstract class VirtualSerialPort : IDisposable
{
    SerialPortEventHandler _eventHandler;

    /// <summary>
	/// Returns <n>true</n> if the derived class supports character-based text mode.
    /// </summary>
	///
	/// <remarks>
	/// <para>
	/// If character-based text mode is supported, the following members are implemented:
	/// <r>Open</r>, <r>ReadExisting</r>, <r>Write</r><c>(string)</c>, <r>Dispose</r>, and
	/// <r>DataReceived</r>.
	/// </para>
	/// <para>
	/// The <r>VirtualSerialPort</r> base class returns <n>false</n>.
	/// </para>
	/// </remarks>
	///
	public virtual bool SupportsCharacterBasedTextMode
	{
		get
		{
			return false;
		}
	}

    /// <summary>
	/// Returns <n>true</n> if the derived class supports line-based text mode.
    /// </summary>
	///
	/// <remarks>
	/// <para>
	/// Some HomeUX device simulators (e.g. LutronSimulator) implement only line-based text mode
	/// because it simplifies their implementation: they don't need to parse lines from a stream of
	/// characters.
	/// </para>
	/// <para>
	/// If line-based text mode is supported, the following members are implemented:
	/// <r>NewLine</r>, <r>Open</r>, <r>ReadExisting</r>, <r>WriteLine</r>, <r>Dispose</r>, and
    /// <r>DataReceived</r>.
	/// </para>
	/// <para>
	/// The <r>VirtualSerialPort</r> base class returns <n>false</n>.
	/// </para>
	/// </remarks>
	///
	public virtual bool SupportsLineBasedTextMode
	{
		get
		{
			return false;
		}
	}

    /// <summary>
	/// Returns <n>true</n> if the derived class supports binary mode.
    /// </summary>
	///
	/// <remarks>
	/// <para>
	/// If binary mode is supported, the following members are implemented: <r>Open</r>,
	/// <r>BytesToRead</r>, <r>Read</r><c>(Byte[], Int32, Int32)</c>, <r>Write</r><c>(Byte[],
	/// Int32, Int32)</c>, <r>Dispose</r>, and <r>DataReceived</r>.
	/// </para>
	/// <para>
	/// The <r>VirtualSerialPort</r> base class returns <n>false</n>.
	/// </para>
	/// </remarks>
	///
	public virtual bool SupportsBinaryMode
	{
		get
		{
			return false;
		}
	}

    /// <summary>
    /// Behaves similar to <n>SerialPort.NewLine</n>: Gets or sets the string written at the end of
	/// each line when <r>WriteLine</r> is called.  Supported if the derived class supports
	/// line-based text mode.
    /// </summary>
	///
	/// <remarks>
	/// <para>
    /// Note that certain virtual serial port implements may assume and require a specific value
	/// for <r>NewLine</r> -- in those cases, if the application attempts to set a different value
	/// an <r>VirtualSerialPortException</r> is thrown.
	/// </para>
	/// <para>
	/// The <r>VirtualSerialPort</r> base class throws <n>NotImplementedException</n>.
	/// </para>
	/// </remarks>
	///
	/// <exception cref="NotImplementedException">
	/// The derived class doesn't support text mode.  See <r>SupportsCharacterBasedTextMode</r> and
	/// <r>SupportsLineBasedTextMode</r>.
	/// </exception>
	///
	/// <exception cref="VirtualSerialPortException">
	/// <r>NewLine</r> is being set to a value not supported by the derived class.
	/// </exception>
    ///
    public virtual string NewLine
	{
		get
		{
			throw new NotImplementedException();
		}
		set
		{
			throw new NotImplementedException();
		}
	}

    /// <summary>
    /// Indicates if <r>Dispose</r> has been called.
    /// </summary>
    ///
    public bool IsDisposed { get; private set; }

    public static VirtualSerialPort GetPort(string portSpec)
    {
        if (portSpec.Contains(";"))
        {
            try
            {
                return Util.CreateInstance<VirtualSerialPort>(portSpec);
            }
            catch (CreateInstanceException ex)
            {
                throw new VirtualSerialPortException(ex,
                    String.Format("Failed to open virtual serial port \"{0}\"", portSpec));
            }
        }
        else
        {
#if SILVERLIGHT
            throw new NotImplementedException("VirtualSerialPort.GetPort is not implemented on Silverlight");
#else
            return new ActualSerialPort(SerialParameters.Parse(portSpec));
#endif
        }
    }

    /// <summary>
    /// Opens the serial port connection.  If the serial port read buffer contains data,
    /// <r>DataReceived</r> will be called.
    /// </summary>
	///
	/// <remarks>
	/// The <r>VirtualSerialPort</r> base class does nothing.
	/// </remarks>
    /// 
    /// <exception cref="VirtualSerialPortException">
    /// <r>Open</r> was already called.
    /// </exception>
    ///
    public virtual void Open()
	{
	}

    /// <summary>
    /// Behaves similar to <n>SerialPort.ReadExisting</n>: Reads all immediately available
	/// characters.  Supported if the derived class implements text mode.
    /// </summary>
	///
	/// <remarks>
	/// The <r>VirtualSerialPort</r> base class throws <n>NotImplementedException</n>.
	/// </remarks>
    /// 
    /// <exception cref="VirtualSerialPortException">
    /// <r>Open</r> was not called before calling this method.
    /// </exception>
	///
	/// <exception cref="NotImplementedException">
	/// The derived class doesn't support text mode.  See <r>SupportsCharacterBasedTextMode</r> and
	/// <r>SupportsLineBasedTextMode</r>.
	/// </exception>
	///
    public virtual string ReadExisting()
	{
		throw new NotImplementedException();
	}

    /// <summary>
    /// Behaves similar to <n>SerialPort.BytesToRead</n>: Gets the number of bytes of data in the
	/// receive buffer.  Supported if the derived class implements binary mode.
    /// </summary>
	///
	/// <remarks>
	/// The <r>VirtualSerialPort</r> base class throws <n>NotImplementedException</n>.
	/// </remarks>
    /// 
    /// <exception cref="VirtualSerialPortException">
    /// <r>Open</r> was not called before calling this method.
    /// </exception>
	///
	/// <exception cref="NotImplementedException">
	/// The derived class doesn't support binary mode.  See <r>SupportsBinaryMode</r>.
	/// </exception>
	///
	public virtual int BytesToRead
	{
		get
		{
			throw new NotImplementedException();
		}
	}

    /// <summary>
    /// Behaves similar to <n>SerialPort.Read</n><c>(Byte[], Int32, Int32)</c>: Reads a number of
	/// bytes from the virtual serial port input buffer and writes those bytes into a byte array at
	/// the specified offset.  Supported if the derived class implements binary mode.
    /// </summary>
	///
	/// <param name="buffer">The byte array to write the input to.</param>
	///
	/// <param name="offset">The offset in the buffer array to begin writing.</param>
	///
	/// <param name="count">The number of bytes to read.</param>
	///
	/// <remarks>
	/// The <r>VirtualSerialPort</r> base class throws <n>NotImplementedException</n>.
	/// </remarks>
    /// 
    /// <returns>
    /// The number of bytes read.
    /// </returns>
    /// 
    /// <exception cref="VirtualSerialPortException">
    /// <r>Open</r> was not called before calling this method.
    /// </exception>
	///
	/// <exception cref="NotImplementedException">
	/// The derived class doesn't support binary mode.  See <r>SupportsBinaryMode</r>.
	/// </exception>
	///
	public virtual int Read(byte[] buffer, int offset, int count)
	{
		throw new NotImplementedException();
	}

    /// <summary>
    /// Behaves similar to <n>SerialPort.WriteLine</n>: Writes the specified string and the
	/// <r>NewLine</r> value to the serial port.  Supported if the derived class implements
	/// line-based text mode.
    /// </summary>
    ///
    /// <param name="text">The string to write.</param>
	///
	/// <remarks>
	/// The <r>VirtualSerialPort</r> base class throws <n>NotImplementedException</n>.
	/// </remarks>
    /// 
    /// <exception cref="VirtualSerialPortException">
    /// <r>Open</r> was not called before calling this method.
    /// </exception>
	///
	/// <exception cref="NotImplementedException">
	/// The derived class doesn't support text mode.  See <r>SupportsLineBasedTextMode</r>.
	/// </exception>
    ///
    public virtual void WriteLine(string text)
	{
		throw new NotImplementedException();
	}

    /// <summary>
    /// Behaves similar to <n>SerialPort.Write</n><c>(String)</c>: Writes the specified string to
	/// the serial port.  Supported if the derived class implements character-based text mode.
    /// </summary>
    ///
    /// <param name="text">The string to write.</param>
	///
	/// <remarks>
	/// The <r>VirtualSerialPort</r> base class throws <n>NotImplementedException</n>.
	/// </remarks>
    /// 
    /// <exception cref="VirtualSerialPortException">
    /// <r>Open</r> was not called before calling this method.
    /// </exception>
	///
	/// <exception cref="NotImplementedException">
	/// The derived class doesn't support text mode.  See <r>SupportsCharacterBasedTextMode</r>.
	/// </exception>
    ///
    public virtual void Write(string text)
	{
		throw new NotImplementedException();
	}

    /// <summary>
    /// Behaves similar to <n>SerialPort.Write</n><c>(Byte[], Int32, Int32)</c>: Writes a specified
	/// number of bytes from a buffer to the virtual serial port.  Supported if the derived class
	/// implements binary mode.
    /// </summary>
	///
	/// <param name="buffer">The byte array to write the from.</param>
	///
	/// <param name="offset">The offset in the buffer array to begin writing.</param>
	///
	/// <param name="count">The number of bytes to write.</param>
	///
	/// <remarks>
	/// The <r>VirtualSerialPort</r> base class throws <n>NotImplementedException</n>.
	/// </remarks>
    /// 
    /// <exception cref="VirtualSerialPortException">
    /// <r>Open</r> was not called before calling this method.
    /// </exception>
	///
	/// <exception cref="NotImplementedException">
	/// The derived class doesn't support binary mode.  See <r>SupportsBinaryMode</r>.
	/// </exception>
	///
	public virtual void Write(byte[] buffer, int offset, int count)
	{
		throw new NotImplementedException();
	}

    /// <summary>
    /// Disposes of resources used by this object. 
    /// </summary>
    ///
    public virtual void Dispose()
    {
        // stop receiving events
        HandleEvents(null, null, null);

        // update state
        IsDisposed = true;
    }

    /// <summary>
    /// Identical to <n>SerialPort.DataReceived</n>, but with slightly different event parameters
	/// (see <r>VirtualSerialDataReceivedEventHandler</r>).
    /// </summary>
	///
    public event VirtualSerialDataReceivedEventHandler DataReceived;

    /// <summary>
    /// Fires the <r>DataReceived</r> event.
    /// </summary>
    ///
    /// <param name="eventType">The type of serial event.</param>
    ///
    protected void FireDataReceived(SerialData eventType)
    {
        if (DataReceived != null)
            DataReceived(this, eventType);
    }

    /// <summary>
    /// Hooks up one or more events fired by an object based on <r>VirtualSerialPort</r>, so that
	/// the caller-supplied delegates that are called when the events fire are called on the
	/// thread of a given <r>WorkQueue</r>.
    /// </summary>
	///
    /// <param name="workQueue">When events are fired by the serial port, work queue items are
	/// 	placed on this <r>WorkQueue</r>.  May be <n>null</n> if both <pr>bytesReceived</pr>
    /// 	and <pr>endOfFileReceived</pr> are <n>null</n>.</param>
	///
    /// <param name="bytesReceived">A delegate to be called when one or more bytes have been
	/// 	received on the serial port, or <n>null</n> if no callback is required.</param>
	///
    /// <param name="endOfFileReceived">A delegate to be called when an end-of-file signal has
	/// 	been received on the serial port, or <n>null</n> if no callback is required.</param>
	///
	/// <remarks>
    /// <para>
	/// This method should be called after TODO-GetSerialPort-??? and before
	/// TODO-OpenWithExceptionHandling-???.
    /// </para>
    /// <para>If both <pr>bytesReceived</pr> and <pr>endOfFileReceived</pr> are <n>null</n>,
    /// event handling is ended, and references to <pr>bytesReceived</pr> and
    /// <pr>endOfFileReceived</pr> from previous calls are released.  This happens automatically
    /// when <r>Dispose</r> is called.</para>
	/// </remarks>
	///
    public void HandleEvents(WorkQueue workQueue, Action bytesReceived,
		Action endOfFileReceived)
    {
        if (_eventHandler != null)
        {
            DataReceived -= _eventHandler.DataReceived;
            _eventHandler = null;
        }

        if ((bytesReceived != null) || (endOfFileReceived != null))
        {
            _eventHandler = new SerialPortEventHandler(workQueue, bytesReceived, endOfFileReceived);
            DataReceived += _eventHandler.DataReceived;
        }
    }

    /// <summary>
    /// Helper class used by <r>HandleEvents</r>.
    /// </summary>
    ///
    class SerialPortEventHandler
    {
		/// <summary>
		/// The <r>WorkQueue</r> that event callback methods will be queued on.
		/// </summary>
        WorkQueue _workQueue;

		/// <summary>
		/// A delegate that will be called when one or more bytes have been received on the serial
		/// port, or <n>null</n> if no callback is provided.
		/// </summary>
        Action _bytesReceived;

		/// <summary>
		/// A delegate that will be called when an end-of-file signal has been received on the
		/// serial port, or <n>null</n> if no callback is provided.
		/// </summary>
		Action _endOfFileReceived;

		/// <summary>
		/// Initializes an instance of this class.
		/// </summary>
		///
		/// <param name="workQueue">The value to use for <r>_workQueue</r>.</param>
		///
		/// <param name="bytesReceived">The value to use for <r>_bytesReceived</r>.</param>
		///
		/// <param name="endOfFileReceived">The value to use for <r>_endOfFileReceived</r>.
		/// 	</param>
		///
        public SerialPortEventHandler(WorkQueue workQueue, Action bytesReceived,
			Action endOfFileReceived)
		{
			_workQueue = workQueue;
			_bytesReceived = bytesReceived;
			_endOfFileReceived = endOfFileReceived;
		}

		/// <summary>
		/// Called when data is received on the serial port.
		/// </summary>
		///
		/// <param name="sender">The serial port.</param>
		///
		/// <param name="eventType">The type of data received.</param>
		///
        public void DataReceived(VirtualSerialPort sender, SerialData eventType)
        {
            if (eventType == SerialData.Chars)
			{
				if (_bytesReceived != null)
					_workQueue.Add(_bytesReceived);
			}
            else
			{
				if (_endOfFileReceived != null)
					_workQueue.Add(_endOfFileReceived);
			}
        }
    }
}

/// <summary>
/// Identical to <n>SerialDataReceivedEventHandler</n>, but with more specifically typed event
/// parameters.
/// </summary>
///
/// <param name="sender">The sender of the event.</param>
///
/// <param name="eventType">The type of serial event.</param>
///
public delegate void VirtualSerialDataReceivedEventHandler(VirtualSerialPort sender,
	SerialData eventType);

#if !SILVERLIGHT
/// <summary>
/// A <r>VirtualSerialPort</r> implementation that implements a real serial port.
/// </summary>
///
public class ActualSerialPort : VirtualSerialPort
{
	/// <summary>
	/// This object is locked before a thread accesses this object, to serialize access to the
	/// object.
	/// </summary>
	object _lock = new object();

    /// <summary>
    /// The wrapped serial port.
    /// </summary>
    SerialPort _serialPort;

    /// <summary>
    /// Set to <n>true</n> once <r>VirtualSerialPort.Open</r> is called.
    /// </summary>
    bool _isOpen;

    /// <summary>
    /// Initializes a new instance of the <r>ActualSerialPort</r> class, specifying serial
	/// communication parameters using a <r>SerialParameters</r> object.
    /// </summary>
    ///
    /// <param name="serialParameters">Serial communication parameters.</param>
    ///
    /// <exception cref="ArgumentException">
    /// One of the properties of <pr>serialParameters</pr> out of range.
    /// </exception>
    ///
    public ActualSerialPort(SerialParameters serialParameters)
	{
        _serialPort = new SerialPort(serialParameters.PortName, serialParameters.BaudRate,
            serialParameters.Parity, serialParameters.DataBits, serialParameters.StopBits);
        _serialPort.DtrEnable = serialParameters.DtrEnable;
        _serialPort.RtsEnable = serialParameters.RtsEnable;
        _serialPort.DataReceived += new SerialDataReceivedEventHandler(
            delegate(object sender, SerialDataReceivedEventArgs e)
            {
                FireDataReceived(e.EventType);
            });
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////
    // VirtualSerialPort Overrides
    //

    /// <summary>
	/// Returns <n>true</n> since this class supports character-based text mode.
    /// </summary>
	///
    public override bool SupportsCharacterBasedTextMode
    {
        get
        {
			return true;
        }
    }

    /// <summary>
	/// Returns <n>true</n> since this class supports line-based text mode.
    /// </summary>
	///
    public override bool SupportsLineBasedTextMode
    {
        get
        {
			return true;
        }
    }

    /// <summary>
	/// Returns <n>true</n> since this class supports binary mode.
    /// </summary>
	///
    public override bool SupportsBinaryMode
    {
        get
        {
			return true;
        }
    }

    /// <summary>
    /// Behaves similar to <n>SerialPort.NewLine</n>: Gets or sets the value used to interpret the
	/// end of a call to <r>WriteLine</r>.
    /// </summary>
    ///
	public override string NewLine
	{
		get
		{
			return _serialPort.NewLine;
		}
		set
		{
			_serialPort.NewLine = value;
		}
	}

    /// <summary>
    /// Opens the serial port connection.  If the serial port read buffer contains data,
    /// <ar>DataReceived</ar> will be called.
    /// </summary>
    /// 
    /// <exception cref="VirtualSerialPortException">
    /// <r>Open</r> was already called, or the serial port could not be opened.
    /// </exception>
    ///
    public override void Open()
    {
        if (_isOpen)
            throw new VirtualSerialPortException("TODO:Resources.VirtualSerialPortAlreadyOpen");
		try
		{
			_serialPort.Open();
		}
		catch (Exception ex)
		{
        	if ((ex is InvalidOperationException) || (ex is ArgumentOutOfRangeException) ||
        	    (ex is ArgumentException) || (ex is IOException) ||
				(ex is UnauthorizedAccessException))
			{
				throw new VirtualSerialPortException(ex.Message, ex);
			}
			else
				throw;
		}
        _isOpen = true;
    }

    /// <summary>
    /// Behaves similar to <n>SerialPort.ReadExisting</n>: Reads all immediately available
	/// characters.
    /// </summary>
    /// 
    /// <exception cref="VirtualSerialPortException">
    /// <r>Open</r> was not called before calling this method.
    /// </exception>
	///
    public override string ReadExisting()
	{
        if (!_isOpen)
            throw new VirtualSerialPortException("TODO:Resources.VirtualSerialPortOpenNotCalled");
		lock (_lock)
			return _serialPort.ReadExisting();
	}

    /// <summary>
    /// Behaves similar to <n>SerialPort.BytesToRead</n>: Gets the number of bytes of data in the
	/// receive buffer.
    /// </summary>
    /// 
    /// <exception cref="VirtualSerialPortException">
    /// <r>Open</r> was not called before calling this method.
    /// </exception>
	///
	public override int BytesToRead
	{
		get
		{
			if (!_isOpen)
				throw new VirtualSerialPortException("TODO:Resources.VirtualSerialPortOpenNotCalled");
			return _serialPort.BytesToRead;
		}
	}

    /// <summary>
    /// Behaves similar to <n>SerialPort.Read</n><c>(Byte[], Int32, Int32)</c>: Reads a number of
	/// bytes from the virtual serial port input buffer and writes those bytes into a byte array at
	/// the specified offset.
    /// </summary>
	///
	/// <param name="buffer">The byte array to write the input to.</param>
	///
	/// <param name="offset">The offset in the buffer array to begin writing.</param>
	///
	/// <param name="count">The number of bytes to read.</param>
    /// 
    /// <returns>
    /// The number of bytes read.
    /// </returns>
    /// 
    /// <exception cref="VirtualSerialPortException">
    /// <r>Open</r> was not called before calling this method.
    /// </exception>
	///
	public override int Read(byte[] buffer, int offset, int count)
	{
		return _serialPort.Read(buffer, offset, count);
	}

    /// <summary>
    /// Behaves similar to <n>SerialPort.WriteLine</n>: Writes the specified string and the
	/// <r>NewLine</r> value to the serial port.
    /// </summary>
    ///
    /// <param name="text">The string to write.</param>
    /// 
    /// <exception cref="VirtualSerialPortException">
    /// <r>Open</r> was not called before calling this method.
    /// </exception>
    ///
    public override void WriteLine(string text)
    {
        if (!_isOpen)
            throw new VirtualSerialPortException("TODO:Resources.VirtualSerialPortOpenNotCalled");
        lock (_lock)
			_serialPort.WriteLine(text);
    }

    /// <summary>
    /// Behaves similar to <n>SerialPort.Write</n><c>(String)</c>: Writes the specified string to
	/// the serial port.
    /// </summary>
    ///
    /// <param name="text">The string to write.</param>
	///
    /// <exception cref="VirtualSerialPortException">
    /// <r>Open</r> was not called before calling this method.
    /// </exception>
    ///
    public override void Write(string text)
	{
		_serialPort.Write(text);
	}

    /// <summary>
    /// Behaves similar to <n>SerialPort.Write</n><c>(Byte[], Int32, Int32)</c>: Writes a specified
	/// number of bytes from a buffer to the virtual serial port.
    /// </summary>
	///
	/// <param name="buffer">The byte array to write the from.</param>
	///
	/// <param name="offset">The offset in the buffer array to begin writing.</param>
	///
	/// <param name="count">The number of bytes to write.</param>
	///
    /// <exception cref="VirtualSerialPortException">
    /// <r>Open</r> was not called before calling this method.
    /// </exception>
	///
	public override void Write(byte[] buffer, int offset, int count)
	{
		_serialPort.Write(buffer, offset, count);
	}

    //////////////////////////////////////////////////////////////////////////
    // IDisposable Implementation
    //

    /// <summary>
	/// Closes the serial port, and releases any resources used by it.
    /// </summary>
	///
	/// <remarks>
	/// The best practice is to wait for some amount of time after calling the <r>Dispose</r>
	/// before attempting to open the serial port again, as the port may not be closed instantly.
	/// </remarks>
	///
    public override void Dispose()
    {
		lock (_lock)
		{
            base.Dispose();
			if (_serialPort != null)
			{
				_serialPort.Close();
				_serialPort = null;
			}
		}
    }
}
#endif

/// <summary>
/// Parameters for communicating over a serial port.
/// </summary>
///
public class SerialParameters
{
    ///////////////////////////////////////////////////////////////////////////////////////////////
	// Public Properties
	//
	/// <summary>
	/// The serial port name, e.g. "COM1".
	/// </summary>
	public string PortName { get; set; }

	/// <summary>
	/// The baud rate.
	/// </summary>
	public int BaudRate { get; set; }

	/// <summary>
	/// The parity setting.
	/// </summary>
	public Parity Parity { get; set; }

	/// <summary>
	/// The data bits value.
	/// </summary>
	public int DataBits { get; set; }

	/// <summary>
	/// The stop bits setting.
	/// </summary>
	public StopBits StopBits { get; set; }

    /// <summary>
    /// Indicates whether the Data Terminal Ready (DTR) signal is enabled during serial
	/// communication.
    /// </summary>
    public bool DtrEnable { get; set; }

    /// <summary>
    /// Indicates whether the Request to Send (RTS) signal is enabled during serial communication.
    /// </summary>
    public bool RtsEnable { get; set; }

    ///////////////////////////////////////////////////////////////////////////////////////////////
	// Public Methods
	//

    /// <summary>
    /// Initializes an instance of this class.
    /// </summary>
    ///
    /// <param name="portName">The port to use; for example, "COM1".</param>
    /// 
    /// <param name="baudRate">The baud rate.</param>
    /// 
    /// <param name="parity">One of the <n>SerialPort.Parity</n> values.</param>
    /// 
    /// <param name="dataBits">The data bits value.</param>
    /// 
    /// <param name="stopBits">One of the <n>SerialPort.StopBits</n> values.</param>
    ///
    public SerialParameters(string portName, int baudRate, Parity parity, int dataBits,
        StopBits stopBits)
    {
		PortName = portName;
		BaudRate = baudRate;
		Parity = parity;
		DataBits = dataBits;
		StopBits = stopBits;
	}

    /// <summary>
    /// Converts a string containing serial parameters to a <r>SerialParameters</r> object.
    /// </summary>
    ///
    /// <param name="parameterString">The serial parameter string; for example, "COM1,9600,8,N,1".
	/// 	See <b>Remarks</b>.</param>
	///
	/// <remarks>
	/// <para>
	/// A serial parameter string consists of the following parts, separated by commas, with no
	/// white space.  All but the first are optional, but if a given part is absent then subsequent
	/// parts must also be absent.
	/// </para>
	/// <list type="number">
	/// <item><description>The serial port name; for example, "COM1".</description></item>
	/// <item><description>The baud rate; the default is "9600".</description></item>
	/// <item><description>The data bits setting; the default is "8".</description></item>
	/// <item><description>The parity setting; the default is "N".  Must be one of the following,
	/// 	in uppercase or lowercase: E, M, N, O, S
	/// 	</description></item>
	/// <item><description>The stop bits setting; the default is "N".  Must be one of: 0, 1, 1.5, 2
	/// 	</description></item>
    /// </list>
    /// <para>
    /// After these parts, if all five parts are present, the following flags may be provided,
    /// separated by commas, in any order:
    /// </para>
    /// <list type="number">
    /// <item><description>DtrEnable: Indicates that the Data Terminal Ready (DTR) signal is
	/// 	enabled during serial communication.</description></item>
    /// <item><description>RtsEnable: Indicates that the Request to Send (RTS) signal is enabled
	/// 	during serial communication.</description></item>
    /// </list>
    /// <para>
	/// Note that the order of parameters follows the traditional serial port parameter ordering,
	/// and is not the same as the order of parameters of the
	/// <s>SerialParameters.SerialParameters</s> constructor, which uses the same ordering as
	/// the .NET <n>SerialPort</n> constructor.
	/// </para>
	/// </remarks>
	///
	public static SerialParameters Parse(string parameterString)
	{
		// divide <parameterString> into its parts, and prepart to parse it
		string[] parts = parameterString.Split(',');
		int index = 0;

		// parse <portName>
		string portName = parts[index++];
        if (portName.Length == 0)
            throw new VirtualSerialPortException("TODO:Resource:Serial port name must be specified");

		// parse <baudRate>, if supplied
		int baudRate;
		if (index < parts.Length)
		{
			string part = parts[index++];
			if (!int.TryParse(part, out baudRate))
			{
				throw new VirtualSerialPortException(
					"TODO:Resource:Baud rate must be an integer; \"{0}\" is not", part);
			}
		}
		else
			baudRate = 9600;

		// parse <dataBits>, if supplied
		int dataBits;
		if (index < parts.Length)
		{
			string part = parts[index++];
			if (!int.TryParse(part, out dataBits))
			{
				throw new VirtualSerialPortException(
					"TODO:Resource:Data bits setting must be an integer; \"{0}\" is not", part);
			}
		}
		else
			dataBits = 8;

		// parse <parity>, if supplied
		Parity parity;
		if (index < parts.Length)
		{
			string part = parts[index++];
			switch (part.ToUpper())
			{
			case "E": parity = Parity.Even;  break;
			case "M": parity = Parity.Mark;  break;
			case "N": parity = Parity.None;  break;
			case "O": parity = Parity.Odd;   break;
			case "S": parity = Parity.Space; break;
			default:
				throw new VirtualSerialPortException(
					"TODO:Resource:Parity setting must be one of E/M/N/O/S; \"{0}\" is not", part);
			}
		}
		else
			parity = Parity.None;

		// parse <stopBits>, if supplied
		StopBits stopBits;
		if (index < parts.Length)
		{
			string part = parts[index++];
			switch (part)
			{
			case "0":   stopBits = StopBits.None;         break;
			case "1":   stopBits = StopBits.One;          break;
			case "1.5": stopBits = StopBits.OnePointFive; break;
			case "2":   stopBits = StopBits.Two;          break;
			default:
				throw new VirtualSerialPortException(
					"TODO:Resource:Stop bits setting must be one of 0/1/1.5/2; \"{0}\" is not", part);
			}
		}
		else
			stopBits = StopBits.One;

		// set <result> a SerialParameters object
		var result = new SerialParameters(portName, baudRate, parity, dataBits, stopBits);

        // parse remaining flags
		while (index < parts.Length)
        {
			string part = parts[index++];
            switch (part)
            {
            case "DtrEnable":
                result.DtrEnable = true;
                break;
            case "RtsEnable":
                result.RtsEnable = true;
                break;
            default:
                throw new VirtualSerialPortException(
                    "Unknown flag \"{0}\"", part);
            }
        }

        return result;
	}
}

/// <summary>
/// Indicates an error that occurred when using a <r>VirtualSerialPort</r>.
/// </summary>
///
public class VirtualSerialPortException : Exception
{
    /// <summary>
    /// Creates an instance of this exception class.  Accepts an optional inner exception.
    /// </summary>
    ///
    /// <param name="innerException">The inner exception.</param>
    ///
    /// <param name="message">The exception message.</param>
    ///
    public VirtualSerialPortException(Exception innerException, string message) :
        base(message, innerException)
    {
    }

    /// <summary>
    /// Creates an instance of this exception class.  Formats an exception message.
    /// </summary>
    ///
    /// <param name="format">The format string for the exception message.</param>
	///
    /// <param name="args">Formatting arguments.  If no arguments are provided, <pr>format</pr> is
	/// 	used as-is, without formattiong.</param>
    ///
	public VirtualSerialPortException(string format, params object[] args) :
        this(null, Util.Format(format, args))
	{
	}
}

}

#if SILVERLIGHT

// definitions that VirtualSerialPort needs, but which are not included in Silverlight
namespace System.IO.Ports
{
    public enum SerialData
    {
        Chars = 1,
        Eof = 2,
    }

    public enum Parity
    {
        None = 0,
        Odd = 1,
        Even = 2,
        Mark = 3,
        Space = 4,
    }

    public enum StopBits
    {
        None = 0,
        One = 1,
        Two = 2,
        OnePointFive = 3,
    }
}

#endif
